package com.xkcyy.javabase.io;

import org.springframework.util.StopWatch;

import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * C01_ByteBufferedOutputstream
 *
 * @author xkcyy
 **/
public class C01ByteBufferedOutputStream {
    @FunctionalInterface
    static public interface Consumer<T> {

        void accept(T t) throws IOException;
    }

    public static void main(String[] args) throws IOException {
        // 1. create file
        String cwd = System.getProperty("user.dir") + "/javabase/";
        File dir = new File(cwd + "target/1/");
        if (dir.exists()) dir.delete();
        dir.mkdirs();
        File f1 = new File(cwd + "target/1/t1.dat");
        File f2 = new File(cwd + "target/1/t2.dat");
        File f3 = new File(cwd + "target/1/t3.dat");
        f1.createNewFile();
        f2.createNewFile();
        f3.createNewFile();
        StopWatch sw = new StopWatch();
        sw.start("bufferedStream");
        newByteBufferedSteam(f1.toString());
        sw.stop();

        sw.start("channel map");
        newMap(f2.toString());
        sw.stop();

        sw.start("bio");
        bioTest(f3.toString());
        sw.stop();

        System.out.println(sw.toString());

    }

    public static long FILE_SIZE = 1024L * 1024 * 1024;


    static void newByteBufferedSteam(String path) throws IOException {
        File file = new File(path);
        FileOutputStream fileOps = new FileOutputStream(file);
        try (BufferedOutputStream bufferedOps = new BufferedOutputStream(fileOps)) {
            // 测试添加1G数据
            testData(bufferedOps::write);
            bufferedOps.flush();
        }
    }

    static void newMap(String path) throws IOException {
        File file = new File(path);
        try (FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel()) {
            MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, FILE_SIZE);
            // 测试添加1G数据
            testData(bytes -> {
                ByteBuffer buf = map.slice();
                buf.position(map.position());
                buf.put(bytes);
                map.force();
            });
            fileChannel.close();
            ByteBufferCleaner.clean(map);
        }

    }

    static void bioTest(String path) throws IOException {
        File file = new File(path);
        try (FileOutputStream fileOps = new FileOutputStream(file)) {
            testData(fileOps::write);
        }
    }

    static void nioTest(String path) throws IOException {
        File file = new File(path);
        try (FileOutputStream fileOps = new FileOutputStream(file)) {
            testData(fileOps::write);
        }
    }

    static void testData(Consumer<byte[]> consumer) throws IOException {
        testData(1024 * 1024, 1024, consumer);
    }

    static void testData(long size, int slice, Consumer<byte[]> consumer) throws IOException {
        for (int i = 0; i < size; i++) {
            // 1k 内容
            byte[] bytes = new byte[slice];
            for (int k = 0; k < slice; k++) {
                bytes[k] = (byte) ('a' + (k % 25));
            }
            consumer.accept(bytes);
        }
    }

    public static class ByteBufferCleaner {

        public static void clean(final ByteBuffer buffer) {
            if (buffer == null || !buffer.isDirect() || buffer.capacity() == 0) {
                return;
            }
            invoke(invoke(viewed(buffer), "cleaner"), "clean");
        }

        private static Object invoke(final Object target, final String methodName, final Class<?>... args) {
            return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                try {
                    Method method = method(target, methodName, args);
                    method.setAccessible(true);
                    return method.invoke(target);
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                }
            });
        }

        private static Method method(Object target, String methodName, Class<?>[] args)
                throws NoSuchMethodException {
            try {
                return target.getClass().getMethod(methodName, args);
            } catch (NoSuchMethodException e) {
                return target.getClass().getDeclaredMethod(methodName, args);
            }
        }

        private static ByteBuffer viewed(ByteBuffer buffer) {
            String methodName = "viewedBuffer";
            Method[] methods = buffer.getClass().getMethods();
            for (int i = 0; i < methods.length; i++) {
                if (methods[i].getName().equals("attachment")) {
                    methodName = "attachment";
                    break;
                }
            }

            ByteBuffer viewedBuffer = (ByteBuffer) invoke(buffer, methodName);
            if (viewedBuffer == null)
                return buffer;
            else
                return viewed(viewedBuffer);
        }
    }
}
